package com.ruoyi.yl.process.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.domain.*;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.common.domain.body.ActivityBody;
import com.ruoyi.common.domain.constant.RedisKey;
import com.ruoyi.system.api.constant.YlStatus;
import com.ruoyi.common.domain.dto.CommentDto;
import com.ruoyi.common.domain.dto.OrderDTO;
import com.ruoyi.yl.domain.Users;
import com.ruoyi.yl.process.OrderProcess;
import com.ruoyi.common.domain.constant.enums.MessTopicAndTag;
import com.ruoyi.yl.rocketmq.product.MessageProducer;
import com.ruoyi.common.domain.constant.to.MessageActivityTo;
import com.ruoyi.yl.service.*;
import com.ruoyi.yl.utils.LoginUserUtil;
import com.ruoyi.yl.utils.OrderNoGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OrderProcessImpl implements OrderProcess {


    @Resource
    private RedisService   redisService;

    @Resource
    private IActivityService activityService;

    @Resource
    private IUsersService usersService;

    @Resource
    private IOrdersService ordersService;


    @Resource
    private MessageProducer messageProducer;

    @Resource
    private OrderNoGenerator orderNoGenerator;


    @Resource
    private IPaymentRecordsService paymentRecordsService;


    @Resource
    private IPointRecordsService pointRecordsService;


    public int tryDeductInventory(Long activityId, int quantity) {
        String redisKey = String.format(RedisKey.ACTIVITY_INVENTORY, activityId);
        String lockKey = "lock:init:activity:" + activityId;
        String lockVal = UUID.randomUUID().toString();

        // 1. Lua 扣库存
        Long result = redisService.atomDeduct(redisKey, quantity);

        if (result == null) {
            throw new ServiceException("Redis 扣库存执行失败");
        }

        if (result >= 0) {
            return result.intValue(); // 扣减成功
        }

        if (result == -1L) {
            throw new ServiceException("库存不足");
        }

        // 2. 如果是 -2，缓存丢失 → 加锁保底恢复缓存
        if (result == -2L) {
            Boolean locked = redisService.setIfAbsent(lockKey, lockVal, 5L, TimeUnit.SECONDS);

            if (Boolean.TRUE.equals(locked)) {
                try {
                    Activity act = activityService.getBaseMapper().selectOne(Wrappers.lambdaQuery(Activity.class)
                            .eq(Activity::getStatus, YlStatus.NORMAL.getCode())
                            .eq(Activity::getId, activityId)
                    );
                    if (act == null) {
                        throw new ServiceException("活动不存在");
                    }
                    activityService.preheatInventoryByActivity(act);
                } finally {
                    // 安全释放锁
                    Object val = redisService.getCacheObject(lockKey);
                    if (lockVal.equals(val)) {
                        redisService.deleteObject(lockKey);
                    }
                }
            }

            //这里是大保底
            throw new ServiceException("系统繁忙，请稍后再试", HttpStatus.WARN);
        }

        throw new ServiceException("未知库存异常", HttpStatus.WARN);
    }

    public void verityActivityAndUser(ActivityBody activity) {

        //1.校验活动是否开始
        if (!activityService.verificationActivityHasStart(activity.getActivityId())) {
            throw new ServiceException("当前活动未开始", HttpStatus.WARN);
        }


        //2. 验证用户积分是否正常
        if (30 > SecurityUtils.getLoginUser().getUsers().getCreditScore()) {
            throw new ServiceException("当前用户积分小于30请联系管理员");
        }
        //3.校验用户是否参加
        ordersService.verificationActivityParticipate(activity.getActivityId());

    }

    @Override
    public Orders createOrder(ActivityBody activityBody) {
        Long activityId = activityBody.getActivityId();
        Long merchantId = activityBody.getMerchantId();
        // 步骤一：验证环节
        // 1.1 检查用户是否已参与该活动（避免重复报名）
        //     - 查询 Redis 标志位：若存在，说明用户已报名，直接抛出异常（避免继续执行）
        //     - 若 Redis 中无标志，则进一步查询订单库中是否存在未超时的订单
        //     - 若存在有效订单，也视为已报名，设置 Redis 并抛出异常
        //
        // 1.2 检查活动是否已开始
        //     - 活动是否处于进行中的状态（now >= startTime）
        //     - 缓存策略：优先查 Redis，无则查库后写入缓存
        //
        // 1.3 检查用户信用积分是否足够
        //     - 若不满足要求，提示用户联系客服，不允许创建订单

        verityActivityAndUser(activityBody);

        // 步骤二：扣减库存（使用 Redis Lua 脚本确保原子性）
        //     - 判断库存 key 是否存在
        //     - 扣减失败（库存不足或 key 丢失）直接抛出异常
        //     - 成功则继续执行后续逻辑
        tryDeductInventory(activityId, 1);

        // 步骤三：创建订单（数据库插入）
        //     - 状态设置为未支付
        //     - 设置创建时间、更新时间
        Orders orders = ordersService.create(activityId, merchantId, orderNoGenerator.generate());

        // 步骤四：发送 MQ 消息 - 同步数据库参与人数 + 延迟取消逻辑

        // 4.1 立即发送参与人数递增的消息
        messageProducer.sendMessageWithTagObj(
                MessTopicAndTag.YL_ACTIVITY_INCREASE_PARTICIPATE,
                MessageActivityTo.builder().activityId(activityId).build()
        );

        // 4.2 发送延迟消息，用于25分钟后处理“未支付订单超时取消”逻辑
        //     - 消息消费后修改订单状态为已超时
        //     - 若用户当日取消超过3次，扣除其10分信用积分
        messageProducer.sendDelayMessageObj(
                MessTopicAndTag.YL_ORDER_VERIFICATION_PAY_STATUS,
                MessageActivityTo.builder().orderId(orders.getId()).orderNo(orders.getOrderNo()).activityId(activityId).build(),
                60 * 25L // 延迟时间为 25 分钟
        );

        // 最终返回订单对象
        return orders;
    }


    @Override
    public Boolean verificationOrderPayStatus(MessageActivityTo to) {
        //1. 修改订单状态
        Orders orders = ordersService.getBaseMapper().selectOne(Wrappers.lambdaQuery(Orders.class)
                .eq(Orders::getStatus, YlStatus.NORMAL.getCode())
                .eq(Orders::getOrderNo, to.getOrderNo())
        );
        if (orders == null) {
            log.error("verificationOrderPayStatus:{}", to);
            throw new ServiceException("订单状态异常 orderNo:" + to.getOrderNo());
        }

        if (Orders.CURRENT_TYPE.NO_PAY.getValue().equals(orders.getCurrentStatus())) {
            orders.setCurrentStatus(Orders.CURRENT_TYPE.TIME_OUT.getValue());
            //修改订单状态为已超时
            ordersService.updateById(orders);
            //返还活动数据库和Redis缓存
            activityService.RefundActivityNumber(to.getActivityId());

            return true;
        }
        return false;
    }

    @Override
    public List<OrderDTO> getOrderList(String currentStatus) {
        return ordersService.selectOrderList(currentStatus);

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = RuntimeException.class)
    public void CancelOrder(String orderNo) {


        Orders orders = ordersService
                .getBaseMapper()
                .selectOne(
                        Wrappers
                                .lambdaQuery(Orders.class)
                                .eq(Orders::getOrderNo, orderNo)
                );

        Orders.CURRENT_TYPE currentType = Orders.CURRENT_TYPE.fromValue(orders.getCurrentStatus());
        switch (currentType) {
            case NO_PAY -> activityService.RefundActivityNumber(orders.getActivityId()); //  1. 未支付需要返还Redis和数据库订单量
            case CANCELLED -> throw new ServiceException("订单已取消");
            case TIME_OUT -> throw new ServiceException("订单已超时无法取消");
            case WAITING_COMMENT -> {
                /*
                  1. 判断支付方式
                  2. 积分支付
                  2.1 查询支付记录 查询支付积分
                  2.2 返还积分
                  2.3 记录积分操作
                  3. 微信支付
                  3.1 查询支付记录
                  3.2 返还微信支付
                  3.3 记录订单支付记录 （退款）
                 */
                try {
                    if (Orders.PAYMENT_TYPE.fromValue(orders.getPaymentType()).equals(Orders.PAYMENT_TYPE.POINTS)) {
                        //2.1 积分支付查询本次支付积分
                        int actualPay = orders.getAmount().intValue();
                        //2.2 返还积分
                        Users users = usersService.selectByOpenId(SecurityUtils.getLoginUser().getUsers().getOpenid());
                        Integer oldPoints = users.getPoints();
                        users.setPoints(users.getPoints() + actualPay);
                        usersService.updateById(users);
                        LoginUserUtil.refreshUsers();
                        //2.3 记录积分操作历史
                        pointRecordsService.save(PointRecords.builder()
                                .description("订单取消，返还积分")
                                .createdAt(LocalDateTime.now())
                                .relatedId(orders.getId())
                                .points(+actualPay)
                                .balance(oldPoints + actualPay)
                                .userId(users.getId())
                                .build());
                        //2.4 记录支付退款
                        paymentRecordsService.save(PaymentRecords.builder()
                                .updatedAt(LocalDateTime.now())
                                .orderId(orders.getId())
                                .amount(orders.getAmount())
                                .paymentTime(LocalDateTime.now())
                                .status(YlStatus.NORMAL.getCode())
                                .userId(users.getId())
                                .paymentType(PaymentRecords.PAYMENT_TYPE.POINTS.getValue())
                                .operationType(PaymentRecords.OPERATION_TYPE.REFUND.getValue())
                                .build());

                        //2.5 返还活动数量 数据库&Redis
                        activityService.RefundActivityNumber(orders.getActivityId());

                    } else if (Orders.PAYMENT_TYPE.fromValue(orders.getPaymentType()).equals(Orders.PAYMENT_TYPE.WECHAT)) {
                        //2.1 积分支付查询本次支付积分
                        int actualPay = orders.getAmount().intValue();
                        //2.2 退款
                        //2.3 等待回调

                        //2.3.1 记录支付退款
                        paymentRecordsService.save(PaymentRecords.builder()
                                .updatedAt(LocalDateTime.now())
                                .orderId(orders.getId())
                                .amount(orders.getAmount())
                                .paymentTime(LocalDateTime.now())
                                .status(YlStatus.NORMAL.getCode())
                                .userId(SecurityUtils.getUserId())
                                .paymentType(PaymentRecords.PAYMENT_TYPE.WECHAT.getValue())
                                .operationType(PaymentRecords.OPERATION_TYPE.REFUND.getValue())
                                .build());

                        //2.3.2 返还活动数量 数据库&Redis
                        activityService.RefundActivityNumber(orders.getActivityId());
                    }
                } catch (Exception e) {
                    log.error(e.getMessage());
                    throw new ServiceException("订单取消失败");
                }

                System.out.println();
            }
            case REFUND -> throw new ServiceException("订单已经退款");
            case WAITING_REVIEW -> throw new ServiceException("订单等待评论无法取消");
            case REVIEW_APPROVED -> throw new ServiceException("订单审核已通过无法取消");
            case REVIEW_REJECT -> throw new ServiceException("订单审核流程中无法取消");
            default -> throw new ServiceException("未知异常无法取消");
        }


        //订单标记状态为已取消
        orders.setCurrentStatus(Orders.CURRENT_TYPE.CANCELLED.getValue());
        ordersService.updateById(orders);

    }

    @Override
    /*
     * 表明该类（class）或方法（method）受事务控制
     * @param propagation  设置隔离级别
     * @param isolation 设置传播行为
     * @param rollbackFor 设置需要回滚的异常类，默认为RuntimeException
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = RuntimeException.class)
    public void payOrderPoints(String orderNo) {
        /*
          1. 校验积分是否充足 business_user
          2. 扣除积分 business_user
          3. 修改订单状态 为已支付 business_order
          4. 创建支付记录 business_payment_record
          5. 创建积分变动记录
         */

        //1. 校验用户积分是否足够
        String openid = SecurityUtils.getLoginUser().getUsers().getOpenid();
        Orders orders = ordersService.selectOrdersByOrderNo(orderNo);
        Users users = usersService.selectByOpenId(openid);

        if (orders.getDemandAmount().intValue() > users.getPoints())
            throw new ServiceException("积分余额不足");
        if (!orders.getCurrentStatus().equals(Orders.CURRENT_TYPE.NO_PAY.getValue()))
            throw new ServiceException("该订单已支付");

        try {
            //2. 扣除积分
            usersService.decodePoint(orders.getDemandAmount());

            //3. 修改订单状态
            orders.setCurrentStatus(Orders.CURRENT_TYPE.WAITING_COMMENT.getValue());
            orders.setAmount(orders.getDemandAmount());
            orders.setPaymentTime(LocalDateTime.now());
            orders.setPaymentType(Orders.PAYMENT_TYPE.POINTS.getValue());
            ordersService.updateById(orders);

            //4. 创建支付记录
            paymentRecordsService.save(PaymentRecords.builder()
                    .orderId(orders.getId())
                    .amount(orders.getAmount())
                    .paymentTime(LocalDateTime.now())
                    .userId(users.getId())
                    .operationType(PaymentRecords.OPERATION_TYPE.PAYMENT.getValue())
                    .paymentType(PaymentRecords.PAYMENT_TYPE.POINTS.getValue())
                    .status(YlStatus.NORMAL.getCode())
                    .createdAt(LocalDateTime.now())
                    .updatedAt(LocalDateTime.now())
                    .build());

            //5. 创建积分变动记录
            pointRecordsService.savePoint(PointRecords.builder()
                    .userId(users.getId())
                    .pointType(PointRecords.POINT_TYPE.CONSUMPTION_DEDUCTION.getValue())
                    .points(-orders.getDemandAmount().intValue())
                    .balance(users.getPoints() - orders.getDemandAmount().intValue())
                    .relatedId(orders.getId())
                    .description(PointRecords.POINT_TYPE.CONSUMPTION_DEDUCTION.getDesc())
                    .createdAt(LocalDateTime.now())
                    .build());


        } catch (ServiceException e) {
            log.error(e.getMessage());
            throw new ServiceException("积分支付过程失败");
        }

    }


    @Override
    public CommentDto getCommentInfoByOderNo(String orderNo) {
     return   ordersService.getCommentInfoByOderNo(orderNo);
    }
}
